﻿using JESAI.DistributedLock.Internal;
using System;

namespace JESAI.DistributedLock.ZooKeeper;

/// <summary>
/// 表示指向ZooKeeper节点的路径。构造函数验证输入是否为有效路径。
/// 调用<see cref="ToString"/>以获取路径值。
/// </summary>
public readonly struct ZooKeeperPath : IEquatable<ZooKeeperPath>
{
    internal const char Separator = '/';

    internal static ZooKeeperPath Root { get; } = new ZooKeeperPath("/");

    private readonly string _path;

    /// <summary>
    /// 根据给定的<paramref name="path"/>字符串构造一个新的<see cref="ZooKeeperPath"/>。
    /// </summary>
    public ZooKeeperPath(string path) : this(path, checkPath: true) { }

    private ZooKeeperPath(string path, bool checkPath, string? paramName = null)
    {
        if (path == null) { throw new ArgumentNullException(paramName ?? nameof(path)); }
        if (checkPath && ValidatePath(path) is { } error)
        {
            throw new FormatException($"{paramName ?? nameof(path)} {error.Reason}{(error.Index.HasValue ? $" (index {error.Index})" : string.Empty)}");
        }
        this._path = path;
    }

    internal ZooKeeperPath? GetDirectory()
    {
        if (this == Root) { return null; }
        var lastSeparatorIndex = this._path.LastIndexOf(Separator);
        return lastSeparatorIndex == 0 ? Root : new ZooKeeperPath(this._path.Substring(0, lastSeparatorIndex), checkPath: false);
    }

    /// <summary>
    /// 以字符串形式返回路径值
    /// </summary>
    public override string ToString() => this._path;

    /// <summary>
    /// 根据路径字符串实现相等
    /// </summary>
    public override bool Equals(object obj) => obj is ZooKeeperPath that && this.Equals(that);

    /// <summary>
    /// 根据路径字符串实现相等
    /// </summary>
    public bool Equals(ZooKeeperPath that) => this._path == that._path;

    /// <summary>
    /// 基于路径字符串实现哈希
    /// </summary>
    public override int GetHashCode() => this._path?.GetHashCode() ?? 0;

    /// <summary>
    /// 根据路径字符串实现相等
    /// </summary>
    public static bool operator ==(ZooKeeperPath @this, ZooKeeperPath that) => @this.Equals(that);

    /// <summary>
    /// 根据路径字符串实现不等式
    /// </summary>
    public static bool operator !=(ZooKeeperPath @this, ZooKeeperPath that) => !(@this == that);

    internal ZooKeeperPath GetChildNodePathWithSafeName(string name)
    {
        if (name == null) { throw new ArgumentNullException(nameof(name)); }

        var isRoot = this == Root;
        var safeName = DistributedLockHelpers.ToSafeName(
                name,
                maxNameLength: int.MaxValue, // no max
                convertToValidName: ConvertToValidNodeName
            )
            // 如果ToSafeName添加哈希，它将使用Base64编码，其中可以包含分隔符。我们用不在Base64中的“_”替换，这样输出名称就可以保持安全，而不会削弱哈希值
            .Replace(Separator, '_');
        return new ZooKeeperPath((this == Root ? this._path : (this._path + Separator)) + safeName, checkPath: false);

        string ConvertToValidNodeName(string name)
        {
            // 为了成为有效的节点名称：

            // 不能为空（特殊情况下，这是因为我们的泛型转换方法将空映射到自身）
            if (name.Length == 0) { return "EMPTY"; }

            // 不能是。。，或者（这是根），保留路径“zookeeper”
            // (参阅 https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#ch_zkDataModel)
            switch (name)
            {
                case ".":
                case "..":
                case "zookeeper" when isRoot:
                    return name + "_";
                default:
                    break; // keep going
            }

            if (name.IndexOf(Separator) < 0 // 不得包含路径分隔符
                && !ValidatePath(Separator + name).HasValue) // “/name”必须是有效路径
            {
                return name;
            }

            var converted = name.ToCharArray();
            for (var i = 0; i < name.Length; ++i)
            {
                switch (name[i])
                {
                    // 注意：我们不必替换“”因为只有当“”时它才无效或“…”是一个完整的路径段。由于我们将在哈希上进行追加，这并不重要
                    case Separator: // 分隔符不能出现在名称中，只能出现在路径中
                    case '\0':
                    case char @char when IsNonNullInvalidPathChar(@char):
                        converted[i] = '_'; //替换为占位符
                        break;
                }
            }

            return new string(converted);
        }
    }

    private static (string Reason, int? Index)? ValidatePath(string path)
    {
        // 基于逻辑https://github.com/apache/zookeeper/blob/master/zookeeper-server/src/main/java/org/apache/zookeeper/common/PathUtils.java#L43（引用于https://stackoverflow.com/questions/55463167/node-name-limitations-in-zookeeper)

        if (path.Length == 0) { return ("may not be empty", null); }
        if (path[0] != Separator) { return ("must start with the '/' character", null); }
        if (path.Length == 1) { return null; }
        if (path[path.Length - 1] == Separator) { return ("must not end with the '/' character", path.Length - 1); }

        for (var i = 1; i < path.Length; ++i)
        {
            // 与ConvertToValidNodeName（）保持同步

            switch (path[i])
            {
                case '\0':
                    return ("may not contain the null character", i);
                case Separator when path[i - 1] == Separator:
                    return ("may not contain empty segments", i);
                case '.' when path[i - (path[i - 1] == '.' ? 2 : 1)] == Separator && ((i + 1 == path.Length) || path[i + 1] == Separator):
                    return ("may not be a relative path", i);
                case char @char when IsNonNullInvalidPathChar(@char):
                    return ("invalid character", i);
            }
        }

        return null;
    }

    private static bool IsNonNullInvalidPathChar(char @char) =>
        @char > '\u0000' && @char <= '\u001f'
        || @char >= '\u007f' && @char <= '\u009F'
        || @char >= '\ud800' && @char <= '\uf8ff'
        || @char >= '\ufff0' && @char <= '\uffff';
}
